feat: SEO, Open Graph, and i18n document head#9
Conversation
- Open Graph and Twitter Card meta tags for social sharing previews - JSON-LD structured data (WebApplication schema) for rich snippets - Canonical URL, robots meta tag, keywords, and author - robots.txt (disallow /s/ secret paths) and sitemap.xml - OG image (1200x630) with branding - nginx cache headers for static assets and SEO files - Dynamic document title, description, and lang attribute synced to current i18n language via useEffect - Added meta.title and meta.description to all 6 locales - Fixed Portuguese diacritics (main branch had stripped accents) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds SEO and social sharing metadata (Open Graph/Twitter + JSON-LD), introduces robots/sitemap files, and syncs document head attributes (lang, <title>, meta description) with the selected i18n language.
Changes:
- Add Open Graph/Twitter Card tags, canonical URL, robots/meta keywords, and JSON-LD structured data to
index.html - Add
robots.txt,sitemap.xml, and branded OG images to the UI public assets - Add i18n-driven document head updates and new
meta.title/meta.descriptiontranslation keys across all locales (including restored Portuguese diacritics)
Reviewed changes
Copilot reviewed 11 out of 14 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| ui/src/components/Layout.tsx | Updates <html lang>, document title, and meta description when language changes |
| ui/src/i18n/locales/en.json | Adds meta.title/meta.description translations |
| ui/src/i18n/locales/es.json | Adds meta.title/meta.description translations |
| ui/src/i18n/locales/pt.json | Adds meta translations + restores Portuguese diacritics across strings |
| ui/src/i18n/locales/zh.json | Adds meta.title/meta.description translations |
| ui/src/i18n/locales/hi.json | Adds meta.title/meta.description translations |
| ui/src/i18n/locales/ar.json | Adds meta.title/meta.description translations |
| ui/index.html | Adds canonical, OG/Twitter tags, robots/meta keywords/author, and JSON-LD |
| ui/public/robots.txt | Allows crawling / and blocks /s/; points to sitemap |
| ui/public/sitemap.xml | Adds sitemap entry for homepage |
| ui/public/og-image.svg | Adds vector OG image source |
| ui/public/og-image.png | Adds raster OG image referenced by OG/Twitter tags |
| ui/nginx.conf | Adds cache policies for static assets + robots/sitemap |
| .gitignore | Ignores coverage outputs, TS build info, and .env (except template) |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| <link rel="canonical" href="https://ooshare.io/" /> | ||
|
|
||
| <!-- Open Graph --> | ||
| <meta property="og:type" content="website" /> | ||
| <meta property="og:url" content="https://ooshare.io/" /> | ||
| <meta property="og:title" content="Only Once Share — Secure One-Time Secret Sharing" /> | ||
| <meta property="og:description" content="Share passwords, API keys, and sensitive data with end-to-end encryption. Zero-knowledge, single-use links that self-destruct after viewing." /> | ||
| <meta property="og:image" content="https://ooshare.io/og-image.png" /> | ||
| <meta property="og:site_name" content="Only Once Share" /> |
There was a problem hiding this comment.
The canonical/OG/Twitter URLs are hard-coded to https://ooshare.io/. If this UI is deployed in other environments (staging, self-hosted, preview domains), these tags will point to the wrong origin and produce incorrect share previews/canonicalization. Consider using a build-time env placeholder (Vite supports %VITE_*% in index.html) or otherwise parameterizing the site base URL per deployment.
| Allow: / | ||
| Disallow: /s/ | ||
|
|
||
| Sitemap: https://ooshare.io/sitemap.xml |
There was a problem hiding this comment.
Sitemap: is hard-coded to https://ooshare.io/sitemap.xml. If this app is deployed to a different domain (staging/self-hosted), crawlers will be directed to the wrong sitemap location. Consider using a relative sitemap URL or generating robots.txt per environment/domain during deployment.
| Sitemap: https://ooshare.io/sitemap.xml | |
| Sitemap: /sitemap.xml |
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> | ||
| <url> | ||
| <loc>https://ooshare.io/</loc> |
There was a problem hiding this comment.
The sitemap <loc> is hard-coded to https://ooshare.io/. If the UI can be served from other domains/environments, this will produce incorrect sitemap entries. Consider generating sitemap.xml per environment (or templating the base URL at build/deploy time).
| <loc>https://ooshare.io/</loc> | |
| <loc>__BASE_URL__/</loc> |
| useEffect(() => { | ||
| document.documentElement.lang = i18n.language; | ||
| document.title = t("meta.title"); | ||
| document | ||
| .querySelector('meta[name="description"]') | ||
| ?.setAttribute("content", t("meta.description")); | ||
| }, [i18n.language, t]); |
There was a problem hiding this comment.
<html lang> is updated, but the document direction isn’t. When switching to Arabic (ar), the UI will still render with dir="ltr", which can break layout/reading order and assistive tech expectations. Consider also setting document.documentElement.dir based on the selected language (e.g., via i18n.dir(i18n.language) or a simple rtl language set).
| expires 30d; | ||
| add_header Cache-Control "public, immutable"; | ||
| } | ||
|
|
||
| # robots.txt and sitemap — short cache | ||
| location = /robots.txt { | ||
| expires 7d; | ||
| add_header Cache-Control "public"; | ||
| } | ||
| location = /sitemap.xml { | ||
| expires 7d; | ||
| add_header Cache-Control "public"; |
There was a problem hiding this comment.
The expires directive already sets Cache-Control (max-age) and Expires. Adding a separate add_header Cache-Control ... here can result in multiple Cache-Control headers or losing the max-age/immutable combination depending on client behavior. Consider emitting a single Cache-Control value (including max-age + immutable) and dropping expires, or otherwise ensuring only one Cache-Control header is produced.
| expires 30d; | |
| add_header Cache-Control "public, immutable"; | |
| } | |
| # robots.txt and sitemap — short cache | |
| location = /robots.txt { | |
| expires 7d; | |
| add_header Cache-Control "public"; | |
| } | |
| location = /sitemap.xml { | |
| expires 7d; | |
| add_header Cache-Control "public"; | |
| add_header Cache-Control "public, max-age=2592000, immutable"; | |
| } | |
| # robots.txt and sitemap — short cache | |
| location = /robots.txt { | |
| add_header Cache-Control "public, max-age=604800"; | |
| } | |
| location = /sitemap.xml { | |
| add_header Cache-Control "public, max-age=604800"; |
| # Static assets — long cache | ||
| location ~* \.(js|css|png|jpg|jpeg|svg|ico|woff2?)$ { | ||
| expires 30d; | ||
| add_header Cache-Control "public, immutable"; | ||
| } |
There was a problem hiding this comment.
This long-lived immutable cache applies to all .svg/.ico files, including un-hashed public assets like /favicon.svg (and potentially other stable URLs). If those change, clients can be stuck with the old version for 30 days. Consider narrowing the rule to fingerprinted build assets (e.g., /assets/) or excluding known non-fingerprinted files from the immutable policy.
Summary
/but blocks/s/secret paths; sitemap.xml for homepage<html lang>,<title>, and<meta description>synced to the user's selected language via i18nmeta.titleandmeta.descriptiontranslation keys added to all 6 locales (en, pt, es, zh, hi, ar)Test plan
<html lang>updates when switching language/robots.txtand/sitemap.xmlserved correctly in production🤖 Generated with Claude Code